# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Provides Descriptor class and related classes with methods for parsing info.

18-byte descriptors are found both in the base EDID and in extensions.
The various types of Descriptor all inherit the basic Descriptor object.
"""
import collections

import coordinated_video_timings as cvt_module
import error
import standard_timings
import tools


TYPE_PRODUCT_SERIAL_NUMBER = 'Display Product Serial Number'
TYPE_ALPHANUM_DATA_STRING = 'Alphanumeric Data String (ASCII)'
TYPE_DISPLAY_RANGE_LIMITS = 'Display Range Limits Descriptor'
TYPE_DISPLAY_PRODUCT_NAME = 'Display Product Name'
TYPE_COLOR_POINT_DATA = 'Color Point Data'
TYPE_STANDARD_TIMING = 'Standard Timing Identifiers'
TYPE_DISPLAY_COLOR_MANAGEMENT = 'Display Color Management (DCM) Data'
TYPE_CVT_TIMING = 'CVT 3 Byte Timing Codes'
TYPE_ESTABLISHED_TIMINGS_III = 'Established Timings III'
TYPE_RESERVED = 'Error: Reserved/undefined; do not use'
TYPE_DUMMY = 'Dummy descriptor'
TYPE_MANUFACTURER_SPECIFIED = 'Manufacturer Specified Display Descriptor'
TYPE_DETAILED_TIMING = 'Detailed Timing Descriptor'
SUBTYPE_DISPLAY_RANGE_DEFAULT = 'Default GTF supported'
SUBTYPE_DISPLAY_RANGE_LIMIT_ONLY = 'Range Limits Only - no additional info'
SUBTYPE_DISPLAY_RANGE_2ND_GTF = 'Secondary GTF supported - requires default too'
SUBTYPE_DISPLAY_RANGE_CVT = 'CVT supported'
SUBTYPE_DISPLAY_RANGE_UNKNOWN = 'Unknown'


def GetDescriptor(edid, start, version):
  """Fetches a descriptor object.

  Args:
    edid: The list of bytes that make up the EDID.
    start: The index in the edid at which the descriptor starts.
    version: The string indicating the version of the EDID.

  Returns:
    A descriptor object.
  """
  block = edid[start:start + 18]

  class_map = {
      0xFF: ProductSerialNumberDescriptor,
      0xFE: AlphanumDataStringDescriptor,
      0xFD: GetDisplayRangeDescriptor,
      0xFC: DisplayProductNameDescriptor,
      0xFB: ColorPointDescriptor,
      0xF9: DisplayColorDescriptor,
      0xF8: CoordinatedVideoTimingsDescriptor,
      0xF7: EstablishedTimingsIIIDescriptor,
      0x10: DummyDescriptor
  }

  if block[0] == block[1] == block[2] == 0x00:
    tag = block[3]
    if tag in class_map:
      return class_map[tag](block)
    elif tag == 0xFA:
      return StandardTimingDescriptor(block, version)
    elif tag >= 0x00 and tag <= 0x0F:
      return ManuSpecifiedDescriptor(block)
    else:  # tag >= 0x11 and tag <= 0xF6
      return ReservedDescriptor(block)

  else:  # Detailed timing descriptor
    return DetailedTimingDescriptor(block)


def GetDisplayRangeDescriptor(block):
  """Fetches some type of Display Range Descriptor.

  Args:
    block: The list of bytes that make up this descriptor.

  Returns:
    A Display Range Descriptor object.
  """
  support_flag = block[10]
  if support_flag == 0x00:
    return DisplayRangeDescriptor(block, SUBTYPE_DISPLAY_RANGE_DEFAULT)
  elif support_flag == 0x01:
    return DisplayRangeDescriptor(block, SUBTYPE_DISPLAY_RANGE_LIMIT_ONLY)
  elif support_flag == 0x02:
    return DisplayRangeGTF(block)
  elif support_flag == 0x04:
    return DisplayRangeCVT(block)
  else:
    return DisplayRangeDescriptor(block, SUBTYPE_DISPLAY_RANGE_UNKNOWN)


class Descriptor(object):
  """Defines a single descriptor and its properties."""

  def __init__(self, block, my_type):
    """Creates a Descriptor object.

    Args:
      block: A list of 18-bytes that make up the descriptor.
      my_type: A string that indicates what type of Descriptor this is.
    """
    self._block = block
    self._type = my_type

  @property
  def type(self):
    """Fetches the type of the descriptor.

    Returns:
      A string that indicates the type of the descriptor.
    """
    return self._type

  def GetBlock(self):
    """Fetches the data block.

    Returns:
      A list of bytes that make up this descriptor.
    """
    return self._block

  def CheckErrors(self, index=None):
    """Checks the validity of this descriptor.

    Args:
      index: The integer index of the descriptor being checked.

    Returns:
      A list of error.Error objects. The list is None or empty if no errors.
    """
    pass


class StringDescriptor(Descriptor):
  """Analyzes a String Descriptor."""

  def __init__(self, block, my_type):
    """Creates a StringDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
      my_type: A string that indicates this descriptor is a String Descriptor.
    """
    Descriptor.__init__(self, block, my_type)
    self._type = my_type

  @property
  def string(self):
    """Fetches the alphanumeric string.

    Returns:
      A string of alphanumeric characters that this descriptor encoded.
    """
    alphanum_data = ''

    for x in xrange(5, 18):
      if self._block[x] == 0x0A:
        break
      else:
        alphanum_data += chr(self._block[x])

    return alphanum_data

  def CheckErrors(self, index=None):
    """Checks the StringDescriptor for errors.

    Args:
      index: The integer index of the descriptor.

    Returns:
      A list of error.Error objects, or None.
    """
    if self._block[4] != 0x00:
      loc = '%s %s' % (self._type, '#%d' % index if index else '')
      return [error.Error(loc, 'Byte 5', 0x00, self._block[4])]
    else:
      return None


class ProductSerialNumberDescriptor(StringDescriptor):
  """Analyzes a Product Serial Number Descriptor."""

  def __init__(self, block):
    """Creates a ProductSerialNumberDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    StringDescriptor.__init__(self, block, TYPE_PRODUCT_SERIAL_NUMBER)


class AlphanumDataStringDescriptor(StringDescriptor):
  """Analyzes an Alphanumerical Data String Descriptor."""

  def __init__(self, block):
    """Creates an AlphanumDataStringDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    StringDescriptor.__init__(self, block, TYPE_ALPHANUM_DATA_STRING)


class DisplayProductNameDescriptor(StringDescriptor):
  """Analyzes a Display Product Name Descriptor."""

  def __init__(self, block):
    """Creates a DisplayProductNameDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    StringDescriptor.__init__(self, block, TYPE_DISPLAY_PRODUCT_NAME)


class DisplayRangeDescriptor(Descriptor):
  """Analyzes a Display Range Descriptor."""

  def __init__(self, block, subtype):
    """Creates a DisplayRangeDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
      subtype: A string that indicates the specific type of
          DisplayRangeDescriptor.
    """
    Descriptor.__init__(self, block, TYPE_DISPLAY_RANGE_LIMITS)
    self._subtype = subtype

  @property
  def subtype(self):
    """Fetches the subtype.

    Returns:
      A string specifying the subtype of the DisplayRangeDescriptor.
    """
    return self._subtype

  def _GetMaxVerticalRateOffset(self):
    """Fetches the maximum vertical rate offset.

    Returns:
      An integer specifying the maximum vertical rate offset (in Hz).
    """
    if self._block[4] & 0x02:
      return 255
    else:
      return 0

  def _GetMinVerticalRateOffset(self):
    """Fetches the minimum vertical rate offset.

    Returns:
      An integer specifying the minimum vertical rate offset (in Hz).
    """
    if self._block[4] & 0x03 == 0x03:
      return 255
    else:
      return 0

  def _GetMaxHorizontalRateOffset(self):
    """Fetches the maximum horizontal rate offset.

    Returns:
      An integer specifying the maximum horizontal rate offset (in kHz).
    """
    if (self._block[4] & 0x0C) >> 2:
      return 255
    else:
      return 0

  def _GetMinHorizontalRateOffset(self):
    """Fetches the minimum horizontal rate offset.

    Returns:
      An integer specifying the minimum horizontal rate offset (in kHz).
    """
    if (self._block[4] >> 2) & 0x03 == 0x03:
      return 255
    else:
      return 0

  @property
  def min_vertical_rate(self):
    """Fetches the minimum vertical rate.

    Returns:
      An integer specifying the minimum vertical rate (in Hz).
    """
    return self._block[5] + self._GetMinVerticalRateOffset()

  @property
  def max_vertical_rate(self):
    """Fetches the maximum vertical rate.

    Returns:
      An integer specifying the maximum vertical rate (in Hz).
    """
    return self._block[6] + self._GetMaxVerticalRateOffset()

  @property
  def min_horizontal_rate(self):
    """Fetches the minimum horizontal rate.

    Returns:
      An integer specifying the minimum horizontal rate (in kHz).
    """
    return self._block[7] + self._GetMinHorizontalRateOffset()

  @property
  def max_horizontal_rate(self):
    """Fetches the maximum horizontal rate.

    Returns:
      An integer specifying the maximum horizontal rate (in kHz).
    """
    return self._block[8] + self._GetMaxHorizontalRateOffset()

  @property
  def pixel_clock(self):
    """Fetches the pixel clock.

    Returns:
      An integer specifying the pixel clock (MHz).
    """
    return self._block[9] * 10

  def CheckErrors(self, index=None):
    """Checks for errors.

    Errors may include maximum values being less than minimum values, or
    invalid support flag values.

    Args:
      index: The integer index of this descriptor (1-4).

    Returns:
      A list of error.Error objects.
    """
    errors = []

    max_vert = self.max_vertical_rate
    min_vert = self.min_vertical_rate

    max_hor = self.max_horizontal_rate
    min_hor = self.min_horizontal_rate

    loc = '%s %s' % (self._type, '#%d' % index if index else '')

    if max_vert < min_vert:
      errors.append(error.Error(loc, 'Maximum vertical rate less than minimum',
                                'Max vert: %d\tMin vert: %d' % (max_vert,
                                                                min_vert)))

    if max_hor < min_hor:
      errors.append(error.Error(loc, 'Maximum horizontal rate less than '
                                'minimum', '', 'Max hor: %d\tMin hor: %d' %
                                (max_hor, min_hor)))

    if not self.pixel_clock:
      errors.append(error.Error(loc, 'Pixel clock value invalid', 'Non-zero',
                                self.pixel_clock))

    val = [0x00, 0x01, 0x02, 0x04]

    val_str = '0x00 0x01 0x02 0x04'

    if self._block[10] not in val:
      my_err = error.Error(loc + '- byte 10', 'Invalid value for Video Timing '
                           'Support Flags', val_str, '0x%02X' % self._block[10])
      errors.append(my_err)

    return errors


# NB: This class is untested - no sample EDIDs to check
class DisplayRangeGTF(DisplayRangeDescriptor):
  """Analyzes a Display Range GTF Descriptor (subtype of Display Range)."""

  def __init__(self, block):
    """Creates a DisplayRangeGTF object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    DisplayRangeDescriptor.__init__(self, block, SUBTYPE_DISPLAY_RANGE_2ND_GTF)

  @property
  def start_break_freq(self):
    """Fetches start break frequency.

    Returns:
      A string that indicates start break frequency.
    """
    return '%s kHz' % (self._block[12]*2)

  @property
  def c(self):
    """Fetches the value c for the Generalized Timing Formula (GTF).

    Returns:
      An integer that is the value of c.
    """
    return self._block[13] / 2

  @property
  def m(self):
    """Fetches the value m for the Generalized Timing Formula (GTF).

    Returns:
      An integer that is the value of m.
    """
    return (self._block[15] << 8) + self._block[14]

  @property
  def k(self):
    """Fetches the value k for the Generalized Timing Formula (GTF).

    Returns:
      An integer that is the value of k.
    """
    return self._block[16]

  @property
  def j(self):
    """Fetches the value j for the Generalized Timing Formula (GTF).

    Returns:
      An integer that is the value of j.
    """
    return self._block[17] / 2


class DisplayRangeCVT(DisplayRangeDescriptor):
  """Analyzes a Display Range CVT Descriptor (subtype of Display Range)."""

  _aspect_ratios = [
      [0x80, '4:3 AR'],
      [0x40, '16:9 AR'],
      [0x20, '16:10 AR'],
      [0x10, '5:4 AR'],
      [0x08, '15:9 AR']
  ]

  def __init__(self, block):
    """Creates a DisplayRangeDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    DisplayRangeDescriptor.__init__(self, block, SUBTYPE_DISPLAY_RANGE_CVT)

  @property
  def cvt_version(self):
    """Fetches CVT version - i.e., Version 1.1.

    Returns:
      A string that indicates the version.
    """
    version = (self._block[11] >> 4) & 0x0F
    revision = self._block[11] & 0x0F
    return '%d.%d' % (version, revision)

  @property
  def pixel_clock(self):
    """Fetches the pixel clock, overriding the general method.

    Since CVT Display Range descriptors have an addtional pixel clock field,
    this additional value is subtracted from the original method.

    Returns:
      A float specifying the pixel clock (MHz).
    """
    return (self._block[9] * 10) - self.additional_pixel_clock

  @property
  def additional_pixel_clock(self):
    """Fetches additional pixel clock value.

    Returns:
      A float indicating additional pixel clock value (in MHz).
    """
    return (self._block[12] >> 2) * 0.25

  @property
  def max_active_pixels(self):
    """Fetches maximum active pixels.

    Returns:
      An int describing the limit on horizontal active pixels, or None if no
      limit.
    """
    if self._block[13] == 0x00:
      return None
    else:  # 8 x [Byte13 + (256 x (Byte 12, bits 1-0))]
      byte12_end = self._block[12] & 0x03
      return 8 * (self._block[13] + (256 * byte12_end))

  @property
  def supported_aspect_ratios(self):
    """Fetches supported aspect ratios.

    Returns:
      A dict of strings and bools specifying supported aspect ratios.
    """
    return tools.DictFilter(self._aspect_ratios, self._block[14])

  @property
  def preferred_aspect_ratio(self):
    """Fetches the preferred aspect ratio.

    Returns:
      A string indicating the preferred aspect ratio.
    """
    pref = (self._block[15] >> 5) & 0x07
    if pref >= len(self._aspect_ratios):
      return 'Undefined'
    return self._aspect_ratios[pref][1]

  @property
  def cvt_blanking_support(self):
    """Fetches the CVT blanking support.

    Returns:
      A collections.OrderedDict of string keys and boolean values indicating
      types of CVT blanking support.
    """
    b = collections.OrderedDict()

    b['Standard CVT Blanking'] = bool(self._block[15] & 0x08)
    b['Reduced CVT Blanking'] = bool(self._block[15] & 0x10)

    return b

  @property
  def display_scaling_support(self):
    """Fetches the types of display scaling support.

    Returns:
      A collections.OrderedDict of string keys and boolean values indicating the
      supported types of display scaling.
    """
    d = collections.OrderedDict()

    d['Horizontal Shrink'] = bool(self._block[16] & 0x80)
    d['Horizontal Stretch'] = bool(self._block[16] & 0x40)
    d['Vertical Shrink'] = bool(self._block[16] & 0x20)
    d['Vertical Stretch'] = bool(self._block[16] & 0x10)

    return d

  @property
  def preferred_vert_refresh(self):
    """Fetches preferred vertical refresh rate.

    Returns:
      An int indicating preferred vertical refresh rate in Hz.
    """
    return self._block[17]


# NB: This class is untested
class ColorPointDescriptor(Descriptor):
  """Analyzes a Color Point Descriptor."""

  def __init__(self, block):
    """Creates a ColorPointDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    Descriptor.__init__(self, block, TYPE_COLOR_POINT_DATA)

  @property
  def first_color_point(self):
    """Fetches the first color point in the descriptor.

    Returns:
      A ColorPoint object.
    """
    return ColorPoint(self._block, 5)

  @property
  def second_color_point(self):
    """Fetches the second color point in the descriptor.

    Returns:
      A ColorPoint object.
    """
    return ColorPoint(self._block, 10)

  def CheckErrors(self, index=None):
    """Checks the ColorPointDescriptor for errors.

    Args:
      index: The integer index of the descriptor.

    Returns:
      A list of error.Error objects.
    """
    errors = []

    if self._block[5] == 0x00:
      errors.append(error.Error('ColorPointDescriptor', 'First color point is '
                                'invalid', 'Non-0 value', 0x00))

    return errors


class ColorPoint(object):
  """Analyzes a single Color Point within a Color Point Descriptor."""

  def __init__(self, block, start):
    """Creates a ColorPoint object.

    Args:
      block: A list of bytes that make up this descriptor.
      start: The start index of the ColorPoint.
    """
    self._block = block[start : (start + 5)]

  @property
  def index_number(self):
    """Fetches the index number.

    Returns:
      An integer that indicates the index number of the Color Point.
    """
    return self._block[0]

  @property
  def white_x(self):
    """Fetches the White X coordinate in the Color Point.

    Returns:
      An integer that indicates the White X coordinate in the Color Point.
    """
    return ((self._block[2] << 2)
            + ((self._block[1] >> 2) & 0x03))

  @property
  def white_y(self):
    """Fetches the White Y coordinate in the Color Point.

    Returns:
      An integer that indicates the White Y coordinate in the Color Point.
    """
    return ((self._block[3] << 2)
            + (self._block[1] & 0x03))

  @property
  def gamma(self):
    """Fetches the gamma value (range 1.00-3.54).

    Returns:
      A float describing the gamma value, or None if unspecified.
    """
    if self._block[4] == 0xFF:
      return None
    else:
      return (self._block[4] + 100) / 100


class StandardTimingDescriptor(Descriptor):
  """Analyzes a Standard Timing Descriptor."""

  def __init__(self, block, version):
    """Creates a StandardTimingDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
      version: The string indicating the version of the EDID.
    """
    Descriptor.__init__(self, block, TYPE_STANDARD_TIMING)
    self._version = version

  @property
  def standard_timings(self):
    """Fetches the standard timings indicated in this descriptor.

    Returns:
      A list of StandardTiming objects.
    """
    sts = []

    for x in xrange(0, 7):
      st = standard_timings.GetStandardTiming(self._block, 5 + (x*2),
                                              self._version)
      if st:
        sts.append(st)

    return sts


class DisplayColorDescriptor(Descriptor):
  """Analyzes a Display Color Descriptor."""

  def __init__(self, block):
    """Creates a DisplayColorDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    Descriptor.__init__(self, block, TYPE_DISPLAY_COLOR_MANAGEMENT)

  @property
  def red_a3(self):
    """Fetches the red a3 value for color management.

    Returns:
      An integer denoting the red a3 value for color management.
    """
    return (self._block[7] << 8) + self._block[6]

  @property
  def red_a2(self):
    """Fetches the red a2 value for color management.

    Returns:
      An integer denoting the red a2 value for color management.
    """
    return (self._block[9] << 8) + self._block[8]

  @property
  def green_a3(self):
    """Fetches the green a3 value for color management.

    Returns:
      An integer denoting the green a3 value for color management.
    """
    return (self._block[11] << 8) + self._block[10]

  @property
  def green_a2(self):
    """Fetches the green a2 value for color management.

    Returns:
      An integer denoting the green a2 value for color management.
    """
    return (self._block[13] << 8) + self._block[12]

  @property
  def blue_a3(self):
    """Fetches the blue a3 value for color management.

    Returns:
      An integer denoting the blue a3 value for color management.
    """
    return (self._block[15] << 8) + self._block[14]

  @property
  def blue_a2(self):
    """Fetches the blue a2 value for color management.

    Returns:
      An integer denoting the blue a2 value for color management.
    """
    return (self._block[17] << 8) + self._block[16]


class CoordinatedVideoTimingsDescriptor(Descriptor):
  """Analyzes a Coordinated Video Timings Descriptor."""

  def __init__(self, block):
    """Creates a CoordinatedVideoTimingsDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    Descriptor.__init__(self, block, TYPE_CVT_TIMING)

  @property
  def coordinated_video_timings(self):
    """Fetches the coordinated video timings.

    Returns:
      A list of coordinated_video_timings.CoordinatedVideoTiming objects.
    """
    cvts = []

    for x in xrange(0, 4):
      cvt = cvt_module.GetCoordinatedVideoTiming(self._block, 6 + (x*3))
      if cvt:
        cvts.append(cvt)

    return cvts

  def CheckErrors(self, index=None):
    """Checks the CoordinatedVideoTimingsDescriptor for errors.

    Args:
      index: The integer index of the descriptor.

    Returns:
      A list of error.Error objects.
    """
    errors = []
    cvts = self.coordinated_video_timings

    for x in xrange(0, len(cvts)):
      err = cvts[x].CheckErrors(x + 1)
      if err:
        errors.append(err)

    return errors


class EstablishedTimingsIIIDescriptor(Descriptor):
  """Analyzes an Established Timings III Descriptor."""

  def __init__(self, block):
    """Creates an EstablishedTimingsIIIDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    Descriptor.__init__(self, block, TYPE_ESTABLISHED_TIMINGS_III)
    self._timings = [
        [0x80000000000, '640 x 350 @ 85 Hz'],
        [0x40000000000, '640 x 400 @ 85 Hz'],
        [0x20000000000, '720 x 400 @ 85 Hz'],
        [0x10000000000, '640 x 480 @ 85 Hz'],
        [0x8000000000, '848 x 480 @ 60 Hz'],
        [0x4000000000, '800 x 600 @ 85 Hz'],
        [0x2000000000, '1024 x 768 @ 85 Hz'],
        [0x1000000000, '1152 x 864 @ 75 Hz'],
        [0x800000000, '1280 x 768 @ 60 Hz (RB)'],
        [0x400000000, '1280 x 768 @ 60 Hz'],
        [0x200000000, '1280 x 768 @ 75 Hz'],
        [0x100000000, '1280 x 768 @ 85 Hz'],
        [0x80000000, '1280 x 960 @ 60 Hz'],
        [0x40000000, '1280 x 960 @ 85 Hz'],
        [0x20000000, '1280 x 1024 @ 60 Hz'],
        [0x10000000, '1280 x 1024 @ 85 Hz'],
        [0x8000000, '1360 x 768 @ 60 Hz'],
        [0x4000000, '1440 x 900 @ 60 Hz (RB)'],
        [0x2000000, '1440 x 900 @ 60 Hz'],
        [0x1000000, '1440 x 900 @ 75 Hz'],
        [0x800000, '1440 x 900 @ 85 Hz'],
        [0x400000, '1400 x 1050 @ 60 Hz (RB)'],
        [0x200000, '1400 x 1050 @ 60 Hz'],
        [0x100000, '1400 x 1050 @ 75 Hz'],
        [0x80000, '1400 x 1050 @ 85 Hz'],
        [0x40000, '1680 x 1050 @ 60 Hz (RB)'],
        [0x20000, '1680 x 1050 @ 60 Hz'],
        [0x10000, '1680 x 1050 @ 75 Hz'],
        [0x8000, '1680 x 1050 @ 85 Hz'],
        [0x4000, '1600 x 1200 @ 60 Hz'],
        [0x2000, '1600 x 1200 @ 65 Hz'],
        [0x1000, '1600 x 1200 @ 70 Hz'],
        [0x800, '1600 x 1200 @ 75 Hz'],
        [0x400, '1600 x 1200 @ 85 Hz'],
        [0x200, '1792 x 1344 @ 60 Hz'],
        [0x100, '1792 x 1344 @ 75 Hz'],
        [0x80, '1856 x 1392 @ 60 Hz'],
        [0x40, '1856 x 1392 @ 75 Hz'],
        [0x20, '1920 x 1200 @ 60 Hz (RB)'],
        [0x10, '1920 x 1200 @ 60 Hz'],
        [0x8, '1920 x 1200 @ 75 Hz'],
        [0x4, '1920 x 1200 @ 85 Hz'],
        [0x2, '1920 x 1440 @ 60 Hz'],
        [0x1, '1920 x 1440 @ 75 Hz']
    ]

  @property
  def established_timings(self):
    """Fetches the supported established timings.

    Returns:
      A dict of strings and bools denoting the supported established timings.
    """
    # Bytes 6-10 plus first half of 11
    timing_byte = ((self._block[6] << 36) + (self._block[7] << 28) +
                   (self._block[8] << 20) + (self._block[9] << 12) +
                   (self._block[10] << 4) + (self._block[11] >> 4))
    return tools.DictFilter(self._timings, timing_byte)


# This descriptor is not supposed to be used yet
class ReservedDescriptor(Descriptor):
  """Defines a ReservedDescriptor (which should not yet appear in EDID)."""

  def __init__(self, block):
    """Creates a ReservedDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    Descriptor.__init__(self, block, TYPE_RESERVED)


# This descriptor indicates that the descriptor space is unused
class DummyDescriptor(Descriptor):
  """Defines a Dummy (placeholder) Descriptor."""

  def __init__(self, block):
    """Creates a DummyDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    Descriptor.__init__(self, block, TYPE_DUMMY)

  # First 5 bytes of Dummy Descriptor should be 0x00
  def CheckErrors(self, index=None):
    """Checks the DummyDescriptor for errors.

    Args:
      index: The integer index of this descriptor (1-4).

    Returns:
      A list of error.Error objects.
    """
    errors = []
    loc = '%s %s' % (self._type, '#%d' % index if index else '')

    if self._block[0:5] != [0x00, 0x00, 0x00, 0x10, 0x00]:
      found_header = '0x%02X ' * 5 % tuple(self._block[0:5])
      errors.append(error.Error(loc, 'Bytes 0-4', '0x00 0x00 0x00 0x10 0x00',
                                found_header))
    if self._block[5:18] != [0x00] * 13:
      found_body = '0x%02X ' * 13 % tuple(self._block[5:18])
      errors.append(error.Error(loc, 'Bytes 5-18', 'All 0x00',
                                found_body))

    return errors


class ManuSpecifiedDescriptor(Descriptor):
  """Defines a Manufacturer Specified Descriptor."""

  def __init__(self, block):
    """Creates a ManuSpecifiedDescriptor object.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    Descriptor.__init__(self, block, TYPE_MANUFACTURER_SPECIFIED)

  def GetBlob(self):
    """Fetches the data blob (13-byte manufacturer specified data).

    Returns:
      A list of bytes that make up the data blob.
    """
    return self._block[5:18]


class DetailedTimingDescriptor(Descriptor):
  """Defines a Detailed Timing Descriptor, perhaps the most common type."""

  def __init__(self, block):
    """Creates a DetailedTiming Descriptorobject.

    Args:
      block: A list of 18-bytes that make up this descriptor.
    """
    Descriptor.__init__(self, block, TYPE_DETAILED_TIMING)

  @property
  def pixel_clock(self):
    """Fetches pixel clock measurements.

    Returns:
      A float denoting the pixel clock measurement (in MHz).
    """
    # Bytes 0-1 | Pixel clock in kHz units
    return ((self._block[1] << 8) + (self._block[0])) / 100.0

  @property
  def h_active_pixels(self):
    """Fetches the number of horizontal active pixels.

    Returns:
      An integer denoting the number of horizontal active pixels.
    """
    # Bytes 2&4 | Horizontal active pixels (0-4095)
    return ((self._block[4] & 0xF0) << 4) + self._block[2]

  @property
  def h_blanking_pixels(self):
    """Fetches the number of horizontal blanking pixels.

    Returns:
      An integer denoting the number of horizontal blanking pixels.
    """
    # Bytes 3&4 | Horizontal blanking pixels (0-4095)
    return ((self._block[4] & 0x0F) << 8) + self._block[3]

  @property
  def v_active_lines(self):
    """Fetches the number of vertical active lines.

    Returns:
      An integer denoting the number of vertical active lines
    """
    # Bytes 5&7 | Vertical active lines (0-4095)
    return ((self._block[7] & 0xF0) << 4) + self._block[5]

  @property
  def v_blanking_lines(self):
    """Fetches the number of vertical blanking lines.

    Returns:
      An integer denoting the number of vertical blanking lines.
    """
    # Bytes 6&7 | Vertical blanking lines (0-4095)
    return ((self._block[7] & 0x0F) << 8) + self._block[6]

  @property
  def h_sync_offset(self):
    """Fetches the number of horizontal sync offset pixels.

    Returns:
      An integer denoting the number of horizontal sync offset pixels.
    """
    # Bytes 8&11 | Horizontal sync offset pixels (0-1023)
    return ((self._block[11] & 0xC0) << 2) + self._block[8]

  @property
  def h_sync_pulse(self):
    """Fetches the horizontal sync pulse.

    Returns:
      An integer denoting the horizontal sync pulse.
    """
    # Bytes 9&11 | Horizontal sync pulse width pixels (0-1023)
    return ((self._block[11] & 0x30) << 4) + self._block[9]

  @property
  def v_sync_offset(self):
    """Fetches the number of vertical sync offset pixels.

    Returns:
      An integer denoting the number of vertical sync offset pixels.
    """
    # Bytes 10&11 | Vertical sync offset lines (0-63)
    return ((self._block[11] & 0x0C) << 2) + ((self._block[10] & 0xF0) >> 4)

  @property
  def v_sync_pulse(self):
    """Fetches the vertical sync pulse.

    Returns:
      An integer denoting the vertical sync pulse.
    """
    # Bytes 10&11 | Vertical sync pulse width lines (0-63)
    return ((self._block[11] & 0x03) << 4) + (self._block[10] & 0x0F)

  @property
  def h_display_size(self):
    """Fetches the horizontal display size.

    Returns:
      An integer denoting the horizontal display size.
    """
    # Bytes 12&14 | Horizontal display size (mm) (0-4095)
    return ((self._block[14] & 0xF0) << 4) + self._block[12]

  @property
  def v_display_size(self):
    """Fetches the vertical display size.

    Returns:
      An integer denoting the vertical display size.
    """
    # Bytes 13&14 | Vertical display size (mm) (0-4095)
    return ((self._block[14] & 0x0F) << 8) + self._block[13]

  @property
  def h_border_pixels(self):
    """Fetches the number of horizontal border pixels.

    Returns:
      An integer denoting the number of horizontal border pixels.
    """
    # Byte 15 | Horizontal border pixels (each side; total is 2x)
    return self._block[15]

  @property
  def v_border_lines(self):
    """Fetches the number of vertical border lines.

    Returns:
      An integer denoting the number of vertical border lines.
    """
    # Byte 16 | Vertical border lines (each side; total is 2x)
    return self._block[16]

  @property
  def interlaced(self):
    """Fetches the interlace setting.

    Returns:
      A boolean indicating whether the setting is interlaced or not.
    """
    # Byte 17 | Features bitmap
    # Bit 7 | Interlaced
    return self._block[17] & 0x80 != 0

  @property
  def stereo_mode(self):
    """Fetches the stereo mode.

    Returns:
      A string indicating the stereo mode.
    """
    # Bits 6-5 | Stereo mode
    stereo_bits = (self._block[17] & 0x60) >> 4 + (self._block[17] & 0x01)
    stereo_map = [
        'No stereo',
        'No stereo',
        'Field sequential stereo, right image when stereo sync signal = 1',
        '2-way interleaved stereo, right image on even lines',
        'Field sequential stereo, left image when stereo sync signal = 1',
        '2-way interleaved stereo, left image on even lines',
        '4-way interleaved stereo',
        'Side-by-side interleaved stereo'
    ]

    return stereo_map[stereo_bits]

  @property
  def sync_type(self):
    """Fetches the sync signal definition type.

    Returns:
      A dict of strings and bools indicating sync signal definition types.
    """
    s = collections.OrderedDict()
    s['Type'] = None

    # Bits 4-1 | Sync signal definitions:
    sync_bits = (self._block[17] >> 1) & 0x0F
    if not sync_bits & 0x08:  # Analog sync signal definitions

      if not sync_bits & 0x04:
        s['Type'] = 'Analog Composite Sync'
      else:
        s['Type'] = 'Bipolar Analog Composite Sync'

      s['Serrations'] = bool(sync_bits & 0x02)
      s['Sync on RGB'] = bool(sync_bits & 0x01)

    else:  # Digital sync signal definitions
      if not sync_bits & 0x04:
        s['Type'] = 'Digital Composite Sync'
        s['Serrations'] = bool(sync_bits & 0x02)
      else:
        s['Type'] = 'Digital Separate Sync'
        s['Vertical sync'] = 'Positive' if sync_bits & 0x02 else 'Negative'

      s['Horizontal sync (outside of V-sync)'] = ('Positive' if sync_bits & 0x01
                                                  else 'Negative')

    return s

